/**
  data-grid.c - Created by Timothy Morey on 5/22/2012
*/

#include <libxml/tree.h>
#include <netcdf.h>
#include <stdio.h>
#include <string.h>

#include "data-schema.h"
#include "netcdf-utils.h"
#include "variant.h"

#ifdef __WINDOWS__
#pragma warning(disable:4996)
#endif

int BuildDataGridSchemaEx(int ncid, int varid, DataGridSchema* schema);
int BuildDimensionSchemaEx(int ncid, int dimid, int unlimdim, DimensionSchema* schema);

int BuildDataGridSchema(const char* dataset, const char* varname, DataGridSchema* schema)
{
  int retval = 1;

  int ncstatus = NC_NOERR;
  char logmsg[256];
  int ncid, varid;

  if(dataset && varname && schema)
  {
    memset(schema, 0, sizeof(DataGridSchema));

    if(NC_NOERR != (ncstatus = nc_open(dataset, 0, &ncid)))
    {
      sprintf(logmsg, "Failed to nc_open %s", dataset);
      PrintNetCDFError(logmsg, ncstatus);
      retval = 0;
    }

    if(retval &&
       NC_NOERR != (ncstatus = nc_inq_varid(ncid, varname, &varid)))
    {
      PrintNetCDFError("nc_inq_varid failed", ncstatus);
      retval = 0;
    }

    if(retval)
    {
      retval = BuildDataGridSchemaEx(ncid, varid, schema);
    }
  }

  return retval;
}

int BuildDataSetSchema(const char* dataset, DataSetSchema* schema)
{
  int retval = 1;

  int ncstatus = NC_NOERR;
  char logmsg[256];
  int ncid;
  int nvars;

  if(dataset && schema)
  {
    memset(schema, 0, sizeof(DataGridSchema));
    strcpy(schema->Name, dataset);

    if(NC_NOERR != (ncstatus = nc_open(dataset, 0, &ncid)))
    {
      sprintf(logmsg, "Failed to nc_open %s", dataset);
      PrintNetCDFError(logmsg, ncstatus);
      retval = 0;
    }

    if(retval &&
       NC_NOERR != (ncstatus = nc_inq_nvars(ncid, &nvars)))
    {
      PrintNetCDFError("nc_inq_nvars failed", ncstatus);
      retval = 0;
    }

    for(schema->NVars = 0; retval && schema->NVars < nvars; schema->NVars++)
    {
      retval = BuildDataGridSchemaEx(ncid, schema->NVars, &schema->Variables[schema->NVars]);
    }
  }

  return retval;
}

int GetDimensionIndex(DataGridSchema schema, const char* dimname)
{
  int retval = -1;
  int i = 0;

  for(i = 0; i < schema.NDims; i++)
  {
    if(0 == strcmp(schema.Dims[i].Name, dimname))
    {
      retval = i;
      break;
    }
  }

  return retval;
}

int GetIndex(DataGridSchema schema, int* indices, int nindices)
{
  int retval = -1;
  int d, dd, dimsize;

  if(indices && schema.NDims == nindices)
  {
    retval = 0;

    for(d = nindices - 1; d >= 0; d--)
    {
      dimsize = 1;
      for(dd = d - 1; dd >= 0; dd--)
        dimsize *= schema.Dims[dd].Length;

      retval += indices[d] * dimsize;
    }
  }

  return retval;
}

int GetLength(DataGridSchema schema)
{
  int retval = 1;
  int i;

  for(i = 0; i < schema.NDims; i++)
    retval *= schema.Dims[i].Length;

  return retval;
}

int GetLengthInBytes(DataGridSchema schema)
{
  int len;
	size_t size;
  len = GetLength(schema);
  GetTypeSize(schema.DataType, &size);
  return len * size;
}

int GetSliceLength(DataGridSchema schema)
{
  int retval = 1;
  int i;

  for(i = 0; i < schema.NDims; i++)
    if(! schema.Dims[i].IsUnlimited)
      retval *= schema.Dims[i].Length;

  return retval;
}

int GetSliceLengthInBytes(DataGridSchema schema)
{
  int len;
	size_t size;
  len = GetSliceLength(schema);
  GetTypeSize(schema.DataType, &size);
  return len * size;
}

int ParseDataGridSchema(xmlNodePtr root, DataGridSchema* schema)
{
  int retval = 0;
  xmlChar* value = 0;
  xmlNodePtr child = 0;

  if(root && schema)
  {
    memset(schema, 0, sizeof(DataGridSchema));

    value = xmlGetProp(root, BAD_CAST "Name");
    strcpy(schema->Name, (char*)value);
    xmlFree(value);

    value = xmlGetProp(root, BAD_CAST "DataType");
    schema->DataType = (nc_type)atoi((char*)value);
    xmlFree(value);

    child = root->children;
    while(child)
    {
      if(0 == xmlStrcmp(BAD_CAST "DimensionSchema", child->name))
      {
        DimensionSchema ds;
        int pos;

        if(0 <= (pos = ParseDimensionSchema(child, &ds)))
          schema->Dims[pos] = ds;

        schema->NDims++;
      }
			else if(0 == xmlStrcmp(BAD_CAST "FillValue", child->name))
			{
				ParseVariant(child, &schema->FillValue);
			}

      child = child->next;
    }
  }

  return retval;
}

int ParseDataSetSchema(xmlNodePtr root, DataSetSchema* schema)
{
  int retval = 0;
  xmlChar* value = 0;
  xmlNodePtr child = 0;

  if(root && schema)
  {
    memset(schema, 0, sizeof(DataSetSchema));

    value = xmlGetProp(root, BAD_CAST "Name");
    strcpy(schema->Name, (char*)value);
    xmlFree(value);

    child = root->children;
    while(child)
    {
      if(0 == xmlStrcmp(BAD_CAST "DataGridSchema", child->name))
      {
        ParseDataGridSchema(child, &schema->Variables[schema->NVars++]);
      }

      child = child->next;
    }

    retval = 1;
  }

  return retval;
}

int ParseDimensionSchema(xmlNodePtr root, DimensionSchema* schema)
{
  int retval = -1;
  xmlChar* value = 0;

  if(root && schema)
  {
    memset(schema, 0, sizeof(DimensionSchema));

    value = xmlGetProp(root, BAD_CAST "Name");
    strcpy(schema->Name, (char*)value);
    xmlFree(value);

    value = xmlGetProp(root, BAD_CAST "Length");
    schema->Length = atoi((char*)value);
    xmlFree(value);

    value = xmlGetProp(root, BAD_CAST "IsUnlimited");
    schema->IsUnlimited = atoi((char*)value);
    xmlFree(value);

    value = xmlGetProp(root, BAD_CAST "Order");
    retval = atoi((char*)value);
    xmlFree(value);
  }

  return retval;
}

int SaveDataGridSchema(DataGridSchema schema, xmlNodePtr* root)
{
  int retval = 0;
  xmlNodePtr child;
  char temp[32];
  int i;

  if(root)
  {
    if(! *root)
      *root = xmlNewNode(0, BAD_CAST "DataGridSchema");

    xmlNewProp(*root, BAD_CAST "Name", BAD_CAST schema.Name);

    sprintf(temp, "%d", schema.DataType);
    xmlNewProp(*root, BAD_CAST "DataType", BAD_CAST temp);

		if(VariantTypeNull != schema.FillValue.Type)
		{
			child = xmlNewNode(0, BAD_CAST "FillValue");
			SaveVariant(schema.FillValue, &child);
			xmlAddChild(*root, child);
		}
    
    for(i = 0; i < schema.NDims; i++)
    {
      child = 0;
      if(SaveDimensionSchema(schema.Dims[i], i, &child))
        xmlAddChild(*root, child);
    }

    retval = 1;
  }

  return retval;
}

int SaveDataSetSchema(DataSetSchema schema, xmlNodePtr* root)
{
  int retval = 0;
  xmlNodePtr child;
  int i;

  if(root)
  {
    if(! *root)
      *root = xmlNewNode(0, BAD_CAST "DataSetSchema");

    xmlNewProp(*root, BAD_CAST "Name", BAD_CAST schema.Name);

    for(i = 0; i < schema.NVars; i++)
    {
      child = 0;
      SaveDataGridSchema(schema.Variables[i], &child);
      xmlAddChild(*root, child);
    }

    retval = 1;
  }

  return retval;
}

int SaveDimensionSchema(DimensionSchema schema, int order, xmlNodePtr* root)
{
  int retval = 0;
  char temp[32];

  if(root)
  {
    if(! *root)
      *root = xmlNewNode(0, BAD_CAST "DimensionSchema");

    xmlNewProp(*root, BAD_CAST "Name", BAD_CAST schema.Name);
    
    sprintf(temp, "%d", schema.Length);
    xmlNewProp(*root, BAD_CAST "Length", BAD_CAST temp);

    sprintf(temp, "%d", schema.IsUnlimited);
    xmlNewProp(*root, BAD_CAST "IsUnlimited", BAD_CAST temp);

    sprintf(temp, "%d", order);
    xmlNewProp(*root, BAD_CAST "Order", BAD_CAST temp);

    retval = 1;
  }

  return retval;
}

int BuildDataGridSchemaEx(int ncid, int varid, DataGridSchema* schema)
{
  int retval = 0;
  int ncstatus;
  int dimids[NC_MAX_DIMS];
  int i;
  int unlimdim;

  if(schema)
  {
    // Assuming that somebody already memset schema to 0...

    retval = 1;

    if(retval &&
       NC_NOERR != (ncstatus = nc_inq_var(ncid, varid, schema->Name, 
                                          (nc_type*)&schema->DataType, 
                                          &schema->NDims, dimids, 0)))
    {
      PrintNetCDFError("nc_inq_var failed", ncstatus);
      retval = 0;
    }

    schema->FillValue.Type = schema->DataType;

    if(retval &&
       NC_NOERR != (ncstatus = nc_get_att(ncid, varid, "_FillValue", &schema->FillValue.Value)))
    {
      if(ncstatus == NC_ENOTATT)
      {
        // This simply means the variable doesn't have a _FillValue attribute,
        // which is acceptable.  Make it null so it isn't used.
        memset(&schema->FillValue, 0, sizeof(Variant));
      }
      else
      {
        PrintNetCDFError("nc_get_att failed", ncstatus);
        retval = 0;
      }
    }
        
    if(retval &&
       NC_NOERR != (ncstatus = nc_inq_unlimdim(ncid, &unlimdim)))
    {
      PrintNetCDFError("nc_inq_unlimdim failed", ncstatus);
      unlimdim = -1;
    }
    
    for(i = 0; retval && i < schema->NDims; i++)
    {
      retval = BuildDimensionSchemaEx(ncid, dimids[i], unlimdim, &schema->Dims[i]);
    }
  }

  return retval;
}

int BuildDimensionSchemaEx(int ncid, int dimid, int unlimdim, DimensionSchema* schema)
{
  int retval = 0;
  int ncstatus;

  if(schema)
  {
    // Assuming that somebody already memset schema to 0...

    retval = 1;

    if(dimid == unlimdim)
      schema->IsUnlimited = 1;

    if(NC_NOERR != (ncstatus = nc_inq_dim(ncid, dimid, schema->Name, &schema->Length)))
    {
      PrintNetCDFError("nc_inq_dim failed", ncstatus);
      retval = 0;
    }
  }

  return retval;
}
